Android Binder 分析——懒人的工具(AIDL)
更新日期:
前面说了 binder 的原理,和 parcel,当时也说了实现 IBinder 接口(Bp 端、Bn 端)代码都非常的机械,人工写的话,不仅浪费时间,而且很有可能会出错(复制、粘贴后忘记改某个地方)。所以 android 弄了代码自动生成的东西。然后还美名其曰 XX 语言—— AIDL(Android Interface Definition Language)。这里不研究这个东西的实现代码,只是说说有这么个东西可以用,然后就是其实你不用也可以。
照例先把相关源码位置啰嗦一下(4.4):
|
|
java 层
其实 aidl 这个东西只能在 java 成使用,native 层是用另外一个类似的东西。但是前面也好说了,这个东西就是一个代码自动生成器,你自己手动写,不用也是可以的。我们就来看下 SystemServer(SS)中的典型吧。
纯手工打造
SS 中手动写 binder 接口代码的典型是 ActivityManagerService(AMS)。binder 接口需要实现什么东西,前面原理篇讲得很清楚了,忘记了的回去看下。
ActivityManager 是 sdk 提供给上层 app 使用的接口。ActivityManagerNative (AMN)接口这边的实现(接口端也是要写代码的,调用 binder 接口向服务端发送请求)。IActiviyManager 就是 AMS 继承 binder 定义的接口。最后服务端实现不止 ActivityManagerService.java 这一个文件,有一个专门的 am 包咧,这里不是分析 AMS ,所以列个代表。
ActivitManager.java 不说啥了,就是 sdk 公开的那些接口,它里面的接口实现全都是通过 ActivityManagerNative.getDefault() 转向 AMN 了。所以 interface 端的主要实现在 AMN 中:
|
|
ActivityManagerNative 采用单列设计模式,保证一个进程中只有一份 Bp。这个做应该是提高效率,因为 app 里面先不说应用自己,framework 里面经常随手 Context.getSystemService(“activity”),如果每次都创建新实例,很浪费内存的,而且慢。
来看下 ActivityManagerNative 的继承关系:
|
|
AMN 继承自 Binder (实现 IBinder 接口,所有的远程接口都是这个),实现 IActivityManager, IActivityManager 继承自 IInterface 这个东西就是 binder 提供给客户端的接口,使用者通过继承这个类定义自己的接口。
来看下 AMS 定义的接口:
|
|
AMN 实现上面的接口定义,里面还又个 ActivityManagerProxy(AMP),Proxy,Binder Proxy 啊,Bp 端的实现。android 喜欢把 Bp 端和 Bn 端的接口放在一起实现:
|
|
最后来看看 Bn 端这边的实现:
|
|
上面只是列举了 AM 中的一个接口的实现,剩下那一堆一个一个手动写吧。很麻烦是不是,所以就可以使用代码自动生成工具了(aidl):
代码自动生成
看了上面的 binder 接口的实现方法,感觉对很麻烦。仔细观察上面的 AMS 的代码,会发现其实有不少东西可以由机器来完成的。aidl 就是干这个事情的,下面来看看 SS (其实 SS 中大部分 Servier 是用 aidl 来写接口的)中使用 aidl 的典型: WindowManagerServices (WMS)。
WindowManager.java 这个 sdk 对上层应用提供的接口。然后这里有个 IWindowManager.aidl 的文件。虽然说是代码自动生成,但是开发者还是要写点东西的,人家好歹叫一个语言咧,不写点代码怎么行(傻瓜相机还要按快门咧)。只要在这个 aidl 文件中把要公开的接口按照一定的格式写就行了,那个格式感觉和 java 代码基本一样:
|
|
和上面 AM 的代码对比下,这简直太简单了。然后 Bn 端(WindowManagerService.java)实现真正的业务就可以了(AM 除了要实现 binder 接口,这一步也是不能少的):
|
|
自动生成的 java 代码在 framework 的源码是找不到的,是编译的时候 aidl 工具动态生成的(这里不分析这个工具),在 out/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src
下面,和放 aidl 文件位置对应(WM 的是在 src/core/java/android/view/IWindowManager.java)。然后 IWindowManager.Stub 就在这个文件里面:
|
|
这个东西除了机器生成的排版比较抱歉以外,其它和上面的 AMS 的 AMN 基本上是一个模子印出来的。aidl 自动生成的代码能够根据参数的类型自动调用 parcel 对应的数据打包函数。基本类型都支持,IBinder 类型的也有特殊的函数。如果是自定义类型的话,需要自己实现 Parcelable 接口。
aidl 的参数定义有2个比较有意思的关键字 in 和 out。如果参数前面加了 in 则表示这是个输入型参数,aidl 生成的代码会先通过 Parcelable 接口读取 Bp 端传过来的对象数据,然后调用对应类型的 Parcelable 接口的 CREATOR.createFromParcel 重新示例化对象,达到跨进程传递自定义类型数据的目的。
如果是 out 则表示这个是输出型参数,aidl 生成的代码会调用该类的默认构造参数创建一个对象,然后传递给 Bn 端的真正函数, Bn 端的这个函数的实现,要负责填充对应的数据,然后调用完成后, aidl 的代码会调用 Parcelable 的接口,写入 binder,返回给 Bp 端调用者。来看下 WMS 里面的例子就比较好理解了:
|
|
上面那些东西用机械化的代码自动生成比手写第一快、第二不会出错。SS 中大部分是用 aidl 来实现接口的,除了少数的几个(有可能就是手工写那个几个太麻烦了,才诞生了 aidl 吧)。上层第三应用的服务应该也是可用手工写的,但是估计比 framework 还要麻烦,因为好像有一些接口是 hide 的。
这里说个小插曲,在 core/java/com/android/internal/statusbar/IStatusBar.aidl 中有 oneway 这一个声明:
|
|
还记得前面说 binder 传送过程中有一种 no reply 的情况么。如果声明了这个关键字,那么 aidl 什么的代码是这样的:
|
|
如果你的 binder 接口不需要返回值,oneway 声明会快一点,当然如果你的 binder 接口需要返回值,记得把这个声明去掉,否则会编译报错的(生成的代码 reply 这个变量会没初始化就当返回值用了)。目前 framework 中我就看到 IStatusBar 加这个声明。
这里 aidl 自动生成代码还忽略了一点。上面手工写的,不是搞了个单例模式么,但是 aidl 生成没用这种模式,是不是自动生成的代码,效率就低了些呢。这里从我们经常用的 Context 接口 getSystemService 说起:
|
|
我们先来看下 ServiceFetcher 是什么东西:
|
|
看到上就差不多能明白了,aidl 的 get Bp 的优化是在 Context 中做的。然后继续看那个 SYSTEM_SERVICE_MAP
:
|
|
看到这里就能放心在 Activity 中尽情的调用 getSystemService 了,因为不管你调用多少次,一个进程就只会有一个服务的 Bp 实例而已。
native 层
前面说了 aidl 是 java 层才有的东西。那么 native 是不是就要手动写那一堆麻烦的 binder 接口实现啦。基本上是,但是有点改善,因为 C++ 有宏和模板函数这2个东西。回去看下原理篇中说的 native 层 binder 库中 IInterface.h 中的那几个宏:
DECLARE_META_INTERFACE IMPLEMENT_META_INTERFACE CHECK_INTERFACE
以及衍生出来的 BpInterface 和 BnInterface 这2个模板类。至少提供了一些模板,让子类省了点事。这几个东西回去看原理篇吧,这里不重复说了。
native 层的 SS 本身就不多,4.4 目前就下面几个吧:
然后,普通应用直接不开放 native 层服务的接口。所以估计 android 就拿宏和模板函数凑活下了。
总结
aidl 这个东西还是不错的,原来要写一堆无聊重复的代码,现在只要列一个 aidl 的清单文件就行了。我又要啰嗦一句:android 干了很多事。